<template>
    <tnxel-submit-form ref="form" :model="model" inline v-if="app">
        <h4 class="mt-0 mb-3">批量生成代码</h4>
        <div class="w-100 text-secondary flex-v-center" v-if="app.dependencyInitializing">
            <tnxel-icon value="Loading"/>
            <span>依赖库加载中</span>
        </div>
        <template v-else>
            <div>
                <el-form-item label="代码文件根路径" label-width="114">{{ root }}</el-form-item>
                <el-form-item label="汇集的建表SQL文件">
                    <InputSave v-model="model.sqlPath"
                        title="选择建表SQL文件位置"
                        :defaultPath="root"
                        button-label="确定"
                        :filters="[{name: 'SQL文件', extensions: ['sql']}]"
                        clearable
                        placeholder="未指定不生成"
                    />
                    <tnxel-button type="primary" class="ms-2" link icon="Document" tooltip="预览建表SQL代码"
                        @click="previewSqlCode" v-if="model.sqlPath"/>
                </el-form-item>
                <el-form-item>
                    <template #label>
                        <el-checkbox v-model="model.lombok">使用Lombok</el-checkbox>
                    </template>
                    <span class="form-item-tip ms-0">不推荐使用Lombok，如果确实要使用，也仅限生成Getter和Setter方法</span>
                </el-form-item>
            </div>
            <el-table ref="table"
                :data="tableList"
                :key="tableTime"
                :empty-text="tip"
                row-key="path"
                default-expand-all
                border
                :max-height="tableMaxHeight"
                scrollbar-always-on
            >
                <el-table-column label="序号" prop="ordinal" width="100" header-align="center"
                    :align="hasChildren ? undefined : 'center'"/>
                <el-table-column label="类型" prop="type" width="150" align="center"/>
                <el-table-column label="文件">
                    <template #default="{row}">
                        <div class="flex-v-center">
                            <el-checkbox v-model="row.selected"
                                class="me-2"
                                :disabled="row.disabled"
                                @change="onRowSelected(row)"
                                v-if="row.entityCodeType"/>
                            <tnxel-icon class="text-warning" value="Warning" title="已存在，保存时将覆盖"
                                v-if="row.exists"/>
                            <tnxel-icon class="text-success" value="CirclePlus" title="不存在，保存时将新建" v-else/>
                            <div class="ms-1">{{ row.relativePath }}</div>
                        </div>
                    </template>
                </el-table-column>
                <el-table-column label="分页查询" width="90" align="center">
                    <template #default="{row}">
                        <tnxel-button type="primary"
                            link
                            :icon="'fa-solid '+ (row.queryable ? 'fa-magnifying-glass-arrow-right' : 'fa-magnifying-glass')"
                            tooltip="点击切换"
                            :tooltip-show-after="700"
                            :title="(row.queryable ? '' : '不') + '生成分页查询方法'"
                            @click="toggleQueryable(row)"
                            v-if="isQueryableEnabled(row)"/>
                    </template>
                </el-table-column>
                <el-table-column label="继承" width="60" align="center">
                    <template #default="{row}">
                        <tnxel-button type="primary"
                            link
                            :icon="(row.extended ? 'fa-solid' : 'fa-regular') + ' fa-square-caret-up'"
                            tooltip="点击切换"
                            :tooltip-show-after="700"
                            :title="(row.extended ? '' : '不') + '继承父类的' + row.type"
                            @click="toggleExtended(row)"
                            v-if="isExtendedEnabled(row)"/>
                    </template>
                </el-table-column>
                <el-table-column label="预览" width="70" align="center">
                    <template #default="{row}">
                        <tnxel-button type="primary" link icon="Document" tooltip="预览代码" @click="preview(row)"/>
                    </template>
                </el-table-column>
            </el-table>
            <el-form-item class="mt-4 w-100 me-0">
                <div class="w-100 flex-center">
                    <el-button type="primary" size="large" @click="toSubmit">保存</el-button>
                </div>
            </el-form-item>
            <tnxel-dialog v-model="save.progressing"
                title="保存中"
                :width="600"
                :show-close="false"
                :close-on-click-modal="false"
                :close-on-press-escape="false"
            >
                <div class="text-secondary overflow-ellipsis mb-2">{{ save.currentPath }}</div>
                <el-progress :percentage="saveProgressPercentage" :format="saveProgressFormat"/>
            </tnxel-dialog>
        </template>
    </tnxel-submit-form>
</template>

<script>
import tnx from '@/tnx';
import InputSave from '@/renderer/components/InputSave.vue';
import ModelingApp from '@/logic/modeling/entity/ModelingApp.js';
import TypeKind from '@/logic/modeling/enums/TypeKind.js';
import PersistUtil from '@/logic/modeling/util/persist.js';
import GenerateUtil from '@/logic/modeling/util/generate.js';
import MavenProjectUtil from '@/logic/maven/util/project.js';
import CodingType from '@/logic/coding/model/CodingType.js';

import CodePreview from './components/CodePreview.vue';

const $ = tnx.libs.$;

export default {
    components: {InputSave},
    props: {
        app: ModelingApp,
        classIds: {
            type: Array,
            default: () => [],
        },
    },
    data() {
        let nowTime = new Date().getTime();
        return {
            mavenProject: null,
            tableList: [],
            hasChildren: false,
            tableTime: nowTime,
            dataTime: nowTime,
            tableMaxHeight: undefined,
            model: {
                sqlPath: '',
                lombok: false,
            },
            save: {
                progressing: false,
                total: 0,
                currentPath: null,
                progressedNum: 0,
            },
        };
    },
    computed: {
        tip() {
            return '请在左侧导航树中选择要生成代码的模型类';
        },
        project() {
            return tnx.app.global.modelingProject;
        },
        jdkVersion() {
            return MavenProjectUtil.getVersion(this.project.maven).jdk;
        },
        root() {
            return this.app ? tnx.util.file.getParentDir(this.app.path) : '';
        },
        saveProgressPercentage() {
            return this.save.total ? this.save.progressedNum / this.save.total * 100 : 0;
        },
    },
    watch: {
        classIds() {
            this.initTableDataList();
        },
    },
    mounted() {
        PersistUtil.loadRelativeMavenProject(this.app.path).then(mavenProject => {
            let root = tnx.util.file.getParentDir(this.app.path);
            this.model.sqlPath = GenerateUtil.getPath(root, mavenProject, this.clazz, 'sql');
        });
        PersistUtil.loadRelativeMavenProject(this.app.path).then(mavenProject => {
            this.mavenProject = mavenProject;
            if (this.app.dependencyInitializing) {
                this.$watch('app.dependencyInitializing', () => {
                    if (this.initTableMaxHeight()) { // 避免多次初始化数据
                        this.initTableDataList();
                    }
                });
            } else {
                this.initTableMaxHeight();
                this.initTableDataList();
            }
        });
    },
    methods: {
        initTableMaxHeight() {
            if (this.app?.dependencyInitializing === false && this.tableMaxHeight === undefined) {
                this.$nextTick(() => {
                    let $form = $(this.$refs.form.$el);
                    let maxHeight = $form.height();
                    let children = $form.children();
                    for (let child of children) {
                        let $child = $(child);
                        if (!$child.is('.el-table')) {
                            maxHeight -= $child.outerHeight(true);
                        }
                    }
                    // 确保高度不是刚好40的倍数，以便超高内容可被用户关注到
                    if (maxHeight / 10 % 4 === 0) {
                        maxHeight -= 20;
                    }
                    this.tableMaxHeight = maxHeight;
                });
                return true;
            }
            return false;
        },
        initTableDataList() {
            this.tableList.length = 0;
            this.hasChildren = false;
            if (this.app && this.mavenProject) {
                if (this.classIds.length) {
                    tnx.showLoading();
                    let intervalId = setInterval(() => {
                        if (new Date().getTime() - this.dataTime > 500) {
                            this.tableTime = this.dataTime;
                            clearInterval(intervalId);
                            tnx.hideLoading();
                        }
                    }, 100);
                }
                for (let i = 0; i < this.classIds.length; i++) {
                    let classId = this.classIds[i];
                    let clazz = this.app.findClass(classId);
                    if (clazz) {
                        let path = GenerateUtil.getPath(this.root, this.mavenProject, clazz);
                        tnx.util.file.exists(path).then(exists => {
                            let row = {
                                ordinal: String(i + 1),
                                type: clazz.kind.caption,
                                path: path,
                                relativePath: path.substring(this.root.length),
                                exists: exists,
                                clazz: clazz,
                            };
                            this.tableList.push(row);
                            this.dataTime = new Date().getTime();

                            if (clazz.kind.supports('entity')) {
                                let defaultExtended = GenerateUtil.getDefaultExtended(this.app, clazz);

                                row.children = [];
                                let j = 1;
                                let entityCodeTypeCaptionMapping = GenerateUtil.getEntityCodeTypeCaptionMapping();

                                const addEntityChild = (clazz, entityCodeType, consumer) => {
                                    let childPath = GenerateUtil.getPath(this.root, this.mavenProject, clazz,
                                        entityCodeType);
                                    tnx.util.file.exists(childPath).then(childExists => {
                                        let child = {
                                            ordinal: (i + 1) + '.' + j++,
                                            type: entityCodeTypeCaptionMapping[entityCodeType],
                                            path: childPath,
                                            relativePath: childPath.substring(this.root.length),
                                            exists: childExists,
                                            clazz: clazz,
                                            entityCodeType: entityCodeType,
                                            queryable: true,
                                            extended: defaultExtended,
                                            selected: true,
                                        };
                                        if (typeof consumer === 'function') {
                                            consumer(child);
                                        }
                                        row.children.push(child);
                                        this.dataTime = new Date().getTime();
                                        this.hasChildren = true;
                                    });
                                }

                                for (let entityCodeType in entityCodeTypeCaptionMapping) {
                                    if (entityCodeType !== 'entity' && entityCodeType !== 'sql') {
                                        if (entityCodeType === 'converter') {
                                            let converters = GenerateUtil.getAutoEnumConverters(this.app, clazz);
                                            for (let converterClassName in converters) {
                                                let converterCodingType = new CodingType(converterClassName);
                                                addEntityChild(converterCodingType, entityCodeType, child => {
                                                    let converter = converters[converterClassName];
                                                    Object.assign(child, converter);
                                                });
                                            }
                                        } else {
                                            addEntityChild(clazz, entityCodeType);
                                        }
                                    }
                                }
                            }
                        });
                    }
                }
            }
        },
        isExtendedEnabled(row) {
            if (row.entityCodeType && row.entityCodeType !== 'jpa' && row.entityCodeType !== 'converter') {
                let superClass = this.app.findClass(row.clazz.superClassId, true, true);
                if (superClass) {
                    return superClass.kind.equals(row.clazz.kind);
                }
            }
            return false;
        },
        isQueryableEnabled(row) {
            let entityCodeType = row.entityCodeType;
            return entityCodeType === 'repox' || entityCodeType === 'repoImpl'
                || entityCodeType === 'service' || entityCodeType === 'serviceImpl'
                || entityCodeType === 'controller';
        },
        onRowSelected(row) {
            if (row.entityCodeType.startsWith('repo') || row.entityCodeType.startsWith('service')) {
                this.forSibling(row, sibling => {
                    // 取消选择Repo时，Repox和RepoImpl也需要同步取消
                    if (row.entityCodeType === 'repo') {
                        if (!row.selected) {
                            sibling.selected = false;
                        }
                        sibling.disabled = !row.selected;
                    } else if (row.entityCodeType.startsWith('repo')) {
                        // Repox和RepoImpl选择同步
                        if (sibling.entityCodeType !== 'repo') {
                            sibling.selected = row.selected;
                        }
                    } else {
                        // Service和ServiceImpl选择同步
                        sibling.selected = row.selected;
                    }
                });
            }
        },
        forSibling(row, consumer) {
            let entityRow = this.tableList.find(r => r.clazz === row.clazz);
            if (row.entityCodeType.startsWith('repo')) {
                for (let sibling of entityRow.children) {
                    if (sibling !== row && sibling.entityCodeType.startsWith('repo')) {
                        consumer(sibling);
                    }
                }
            } else if (row.entityCodeType.startsWith('service')) {
                for (let sibling of entityRow.children) {
                    if (sibling !== row && sibling.entityCodeType.startsWith('service')) {
                        consumer(sibling);
                    }
                }
            }
        },
        toggleQueryable(row) {
            row.queryable = !row.queryable;
            this.forSibling(row, sibling => {
                sibling.queryable = row.queryable;
            });
        },
        toggleExtended(row) {
            row.extended = !row.extended;
            this.forSibling(row, sibling => {
                sibling.extended = row.extended;
            });
        },
        generateSqlCode() {
            let key = 'set foreign_key_checks = ';
            let code = key + '0;\n\n';
            for (let row of this.tableList) {
                let clazz = row.clazz;
                if (clazz.kind.supports('entity')) {
                    let superClass = this.app.getSuperClassWithInheritanceStrategy(clazz);
                    code += GenerateUtil.generateSqlCode(this.project, this.app, clazz, superClass) + '\n';
                }
            }
            code += key + '1;\n';
            return code;
        },
        previewSqlCode() {
            if (this.model.sqlPath) {
                let code = this.generateSqlCode();
                this.openPreview(code, 'sql');
            }
        },
        preview(row) {
            let language = undefined;
            if (row.entityCodeType) {
                let entityCodeTypeItem = GenerateUtil.getEntityCodeTypeItem(row.entityCodeType);
                if (entityCodeTypeItem) {
                    language = entityCodeTypeItem.language;
                }
            }
            let code = this.generateCode(row);
            this.openPreview(code, language);
        },
        openPreview(code, language) {
            tnx.open(CodePreview, {
                code: code,
                language: language,
            }, {
                title: '代码预览',
                width: 960,
                type: 'close',
            });
        },
        generateCode(row) {
            let clazz = row.clazz;
            let entityCodeType = row.entityCodeType;
            if (entityCodeType) {
                switch (entityCodeType) {
                    case 'jpa': {
                        let superClass = this.app.getSuperClassWithInheritanceStrategy(clazz);
                        let result = GenerateUtil.generateJpaMappingXmlCode(this.app, clazz, superClass);
                        return result.code;
                    }
                    case 'converter': {
                        return GenerateUtil.generateEnumConverterCode(row.enumClass, row.arrayed);
                    }
                    case 'querying': {
                        return GenerateUtil.generateQueryingCode(this.app, clazz, {
                            lombok: this.model.lombok,
                            extended: row.extended,
                        });
                    }
                    case 'repo': {
                        let entityRow = this.tableList.find(row => row.clazz === clazz);
                        let repoxRow = entityRow.children.find(child => child.entityCodeType === 'repox');
                        return GenerateUtil.generateRepoCode(this.app, clazz, {
                            extended: row.extended,
                            expanded: repoxRow.selected,
                        });
                    }
                    case 'repox': {
                        return GenerateUtil.generateRepoxCode(this.app, clazz, {
                            queryable: row.queryable,
                            extended: row.extended,
                        });
                    }
                    case 'repoImpl': {
                        return GenerateUtil.generateRepoImplCode(this.app, clazz, {
                            queryable: row.queryable,
                            extended: row.extended,
                        });
                    }
                    case 'command': {
                        return GenerateUtil.generateCommandCode(this.app, clazz, {
                            lombok: this.model.lombok,
                            extended: row.extended,
                        });
                    }
                    case 'service': {
                        return GenerateUtil.generateServiceInterfaceCode(this.app, clazz, {
                            queryable: row.queryable,
                            extended: row.extended,
                        });
                    }
                    case 'serviceImpl': {
                        return GenerateUtil.generateServiceImplCode(this.app, clazz, {
                            queryable: row.queryable,
                            extended: row.extended,
                            jdkVersion: this.jdkVersion,
                        });
                    }
                    case 'controller': {
                        return GenerateUtil.generateControllerCode(this.app, clazz, {
                            queryable: row.queryable,
                            extended: row.extended,
                        });
                    }
                }
            } else if (clazz.kind.supports('entity')) {
                return GenerateUtil.generateEntityClassCode(this.project, this.app, clazz, {
                    lombok: this.model.lombok,
                });
            } else if (TypeKind.ENUM.equals(clazz.kind)) {
                return GenerateUtil.generateEnumClassCode(this.app, clazz);
            } else if (TypeKind.PLAIN.equals(clazz.kind)) {
                return GenerateUtil.generatePlainClassCode(this.app, clazz, {
                    lombok: this.model.lombok,
                });
            } else if (TypeKind.VALUE.equals(clazz.kind)) {
                return GenerateUtil.generateValueClassCode(this.app, clazz, {
                    lombok: this.model.lombok,
                });
            }
            return '';
        },
        toSubmit() {
            if (!this.tableList.length) {
                tnx.alert(this.tip);
                return;
            }
            let rows = [];
            for (let row of this.tableList) {
                rows.push(row);
                if (row.children) {
                    for (let child of row.children) {
                        if (child.selected) {
                            rows.push(child);
                        }
                    }
                }
            }
            let total = rows.length;
            if (this.model.sqlPath) {
                total++;
            }
            let message = `确定保存这 ${total} 个文件`;
            if (this.model.sqlPath) {
                message += '（含汇集的建表SQL文件）';
            }
            message += '吗？';
            tnx.confirm(message).then(yes => {
                if (yes) {
                    this.save.currentPath = null;
                    this.save.total = total;
                    this.save.progressedNum = 0;
                    this.save.progressing = true;
                    if (this.model.sqlPath) {
                        this.save.currentPath = this.model.sqlPath;
                        let code = this.generateSqlCode();
                        tnx.util.file.write(this.model.sqlPath, code).then(() => {
                            this.save.progressedNum++;
                            setTimeout(() => {
                                this.doSave(rows);
                            }, 100);
                        }).catch(error => {
                            tnx.error(error);
                        });
                    } else {
                        this.doSave(rows);
                    }
                }
            });
        },
        doSave(rows) {
            if (rows.length) {
                let row = rows.shift();
                this.save.currentPath = row.relativePath;
                let code = this.generateCode(row);
                tnx.util.file.write(row.path, code).then(() => {
                    this.save.progressedNum++;
                    setTimeout(() => {
                        this.doSave(rows);
                    }, 100);
                }).catch(error => {
                    tnx.error(error);
                });
            } else {
                setTimeout(() => {
                    this.save.progressing = false;
                    tnx.confirm(`成功保存 ${(this.save.progressedNum)} 个文件`, '成功', {
                        type: 'success',
                        confirmButtonText: '关闭',
                        cancelButtonText: '留在本页',
                    }).then(yes => {
                        if (yes) {
                            tnx.app.eventBus.emit('tnxtool.modeling.batchGenerateCode', false);
                        }
                    });
                }, 500);
            }
        },
        saveProgressFormat() {
            return this.save.progressedNum + ' / ' + this.save.total;
        },
    }
}
</script>

<style lang="scss" scoped>
:deep(.el-table) {
    .el-checkbox {
        height: 16px;
    }
}
</style>
